import {
  Application,
  Assets,
  Container,
  Graphics,
  Renderer,
  Sprite,
  TextStyle,
  Text,
} from "pixi.js";
import {
  memo,
  useCallback,
  useEffect,
  useRef,
  forwardRef,
  useImperativeHandle,
  useContext,
} from "react";
import styles from "./index.module.scss";
import dayjs from "dayjs";
import customParseFormat from "dayjs/plugin/customParseFormat";
import { IPhotoCanvasData, IPhotoData } from "../../type";
import indexContext from "../../context";

dayjs.extend(customParseFormat);

interface ICanvasRef {
  renderPhotoContainer: (
    photoData: IPhotoData,
    show: boolean,
    ss?: boolean
  ) => Promise<IPhotoData>;
}

const Canvas = forwardRef<ICanvasRef>((_, ref) => {
  const { setPhotoList, photoList, showPhoto } = useContext(indexContext);

  const canvasContainerRef = useRef<HTMLDivElement | null>(null);
  const pixiAppRef = useRef<Application<Renderer> | null>(null); // pixi实例组件
  const canvasDomRef = useRef<HTMLCanvasElement | null>(null); // canvasDom
  const showPhotoRef = useRef<IPhotoData | undefined>(
    showPhoto ? { ...showPhoto } : undefined
  );

  const onDragMove = useCallback((event: any) => {
    if (!showPhotoRef.current || !showPhotoRef.current.canvasData) return;
    const { canvasData, sceneConfig } = showPhotoRef.current;
    const { photoSprite } = canvasData;

    if (photoSprite) {
      const { x, y, height, width } = photoSprite;
      const maxX = 0;
      const minX = -(width - sceneConfig.width);
      const maxY = 0;
      const minY = -(height - sceneConfig.imgHeight);
      let newX = x + event.movement.x;
      newX = newX > maxX ? maxX : newX;
      newX = newX < minX ? minX : newX;
      let newY = y + event.movement.y;
      newY = newY < minY ? minY : newY;
      newY = newY > maxY ? maxY : newY;
      photoSprite.position.set(newX, newY);
    }
  }, []);

  const onDragStart = useCallback(() => {
    pixiAppRef.current?.stage!.on("pointermove", onDragMove);
  }, [onDragMove]);

  const onDragEnd = useCallback(() => {
    pixiAppRef.current?.stage!.off("pointermove", onDragMove);
  }, [onDragMove]);

  // 获取当前图片
  const getImg = useCallback(async (container: Container) => {
    if (!pixiAppRef.current) return;
    const { renderer } = pixiAppRef.current;
    const imgUrl = await renderer.extract.base64({
      target: container,
      format: "jpg",
    });
    return imgUrl;
  }, []);

  // 初始化canvas
  const initCanvas = useCallback(async () => {
    const app = new Application();
    const canvasDom = document.createElement("canvas");
    canvasDom.style.width = `0px`;
    canvasDom.style.height = `0px`;
    canvasDomRef.current = canvasDom;
    canvasDom.className = styles.canvas;
    canvasContainerRef.current?.append(canvasDom);
    await app.init({
      width: 10000,
      height: 10000,
      resolution: 1,
      canvas: canvasDom,
    });
    const { stage, screen } = app;
    stage.cursor = "pointer";
    stage.eventMode = "static";
    stage.hitArea = screen;
    stage.on("pointerup", onDragEnd);
    stage.on("pointerupoutside", onDragEnd);
    stage.on("pointerdown", onDragStart, stage);
    pixiAppRef.current = app;
  }, [onDragEnd, onDragStart]);

  // 重置canvas大小
  const resetCanvasSize = useCallback(async (data: IPhotoData) => {
    const { sceneConfig, photoInfo } = data;
    const width = sceneConfig.totalW;
    const height = sceneConfig.totalH;
    const resolution = photoInfo.width / sceneConfig.totalW;
    console.log("🚀 ~ 重置canvas大小", width, height, resolution);
    await pixiAppRef.current?.renderer.resize(width, height, resolution);
    if (canvasDomRef.current) {
      canvasDomRef.current.style.width = `${width}px`;
      canvasDomRef.current.style.height = `${height}px`;
    }
  }, []);

  // 渲染水印设置
  const renderSign = useCallback(async (photoData: IPhotoData) => {
    const { photoInfo, sceneConfig } = photoData;

    // 照片信息容器配置
    const signContainerConfig = {
      width: sceneConfig.width,
      height: sceneConfig.infoHeight,
      x: sceneConfig.infoPadding,
      y: sceneConfig.imgHeight + sceneConfig.infoPadding,
    };

    // 厂商logo大小
    const logoSize = sceneConfig.infoHeight - sceneConfig.infoPadding * 2;
    // 水印容器
    const signContainer = await new Container(signContainerConfig);
    // 厂商logo
    const makeLogo = await new Sprite({
      // logo 处理
      texture: await Assets.load(
        photoInfo.customLogo ||
          photoInfo.logo ||
          require("../../assets/img/yinan.png")
      ),
      height: logoSize,
      width: logoSize,
    });
    // 相机品牌文本
    const modelText = await new Text({
      text: photoInfo.model === "NIKON Z 30" ? "Nikon Z30" : photoInfo.model,
      x: logoSize + sceneConfig.infoPadding * 2,
      y: 0,
      style: new TextStyle({ fill: "#333", fontSize: 20 }),
    });
    // 曝光信息文本
    const exposureText = await new Text({
      text: `${photoInfo.focus}mm ${photoInfo.shutter} f/${photoInfo.f} ISO${photoInfo.iso}`,
      x: logoSize + sceneConfig.infoPadding * 2,
      y: 35,
      style: new TextStyle({ fill: "#666", fontSize: 16 }),
    });
    // 相机拍摄时间
    const shootTimeText = await new Text({
      text: photoInfo.date,
      y: 35,
      style: new TextStyle({
        fill: "#666",
        fontSize: 16,
      }),
    });
    // 相机拍摄地点
    const positionText = await new Text({
      text: photoInfo.address,
      y: 2,
      style: new TextStyle({ fill: "#666", fontSize: 16 }),
    });
    // 厂商logo分割线
    const graphics = await new Graphics()
      .rect(logoSize + sceneConfig.infoPadding, 0, 1, logoSize)
      .fill("#eee");

    // 拍摄时间右对齐
    shootTimeText.x =
      signContainerConfig.width -
      shootTimeText.width -
      sceneConfig.infoPadding * 2;

    // 定位信息右对齐
    positionText.x =
      signContainerConfig.width -
      positionText.width -
      sceneConfig.infoPadding * 2;
    // 水印容器
    signContainer.addChild(
      makeLogo,
      modelText,
      exposureText,
      shootTimeText,
      positionText,
      graphics
    );

    return {
      signContainer,
      makeLogo,
      modelText,
      exposureText,
      shootTimeText,
      positionText,
      graphics,
    };
  }, []);

  // 渲染图片容器
  const renderPhotoContainer = useCallback(
    async (
      photoData: IPhotoData,
      show = false,
      again = false
    ): Promise<IPhotoData> => {
      const { photoInfo, sceneConfig } = photoData;
      const { isSign } = photoInfo;
      console.log("🚀 ~ 渲染图片:", photoData);

      // 重复渲染，销毁容器
      if (photoData.canvasData) {
        if (again) {
          console.log("🚀 ~ 重新渲染，销毁容器");
          pixiAppRef.current?.stage.removeChild(photoData.canvasData.container);
          photoData.canvasData.container.destroy({
            children: true,
          });
        } else {
          return photoData;
        }
      }

      const photoCanvasData: IPhotoCanvasData = {
        // 图片总容器
        container: await new Container({
          width: sceneConfig.totalW,
          height: sceneConfig.totalH,
        }),
        // 内边距容器 装图片和图片信息  比图片总容器小，实现边框效果
        innerContainer: await new Container({
          width: sceneConfig.width,
          height: sceneConfig.imgHeight,
          x: photoInfo.padding,
          y: photoInfo.padding,
        }),
        // 图片精灵
        photoSprite: new Sprite({
          texture: await Assets.load(photoData.originalPhoto),
          width: sceneConfig.width,
          height: (photoInfo.height / photoInfo.width) * sceneConfig.width,
        }),
      };

      if (isSign) Object.assign(photoCanvasData, await renderSign(photoData));

      const { container, innerContainer, photoSprite, signContainer } =
        photoCanvasData;

      // 为图片总容器添加背景色
      container?.addChild(
        new Graphics()
          .rect(0, 0, sceneConfig.totalW, sceneConfig.totalH)
          .fill("#fff")
      );

      // 图片精灵裁剪蒙层
      photoSprite!.mask = new Graphics()
        .rect(
          photoInfo.padding,
          photoInfo.padding,
          sceneConfig.width,
          sceneConfig.imgHeight
        )
        .fill("transparent");

      innerContainer.addChild(photoSprite);
      if (signContainer) innerContainer.addChild(signContainer);
      container.addChild(innerContainer);

      container.visible = show; // 设置是否可见
      pixiAppRef.current?.stage.addChild(container);

      if (pixiAppRef.current && !photoData.canvasData) {
        const resolution = photoInfo.width / sceneConfig.totalW;
        pixiAppRef.current.renderer.resolution = resolution;
      }

      return {
        ...photoData,
        canvasData: photoCanvasData,
        editPhoto: await getImg(container),
      };
    },
    [getImg, renderSign]
  );

  // 处理图片缩放
  const handleScale = useCallback((type: "add" | "reduce") => {
    if (!showPhotoRef.current || !showPhotoRef.current.canvasData) return;
    const { canvasData } = showPhotoRef.current;
    const { photoSprite } = canvasData;
    let scaleSize = photoSprite.scale.x / 50;
    scaleSize = type === "add" ? scaleSize : -scaleSize;
    photoSprite.scale.set(photoSprite.scale.x + scaleSize);
  }, []);

  // 处理键盘事件
  const handleKeyboardEvent = useCallback(
    async (event: KeyboardEvent) => {
      if (!showPhotoRef.current || !showPhotoRef.current.canvasData) return;
      const { canvasData } = showPhotoRef.current;
      const { photoSprite, container } = canvasData;
      const { code } = event;
      switch (code) {
        case "ArrowUp":
          photoSprite.y -= 1;
          break;
        case "ArrowDown":
          photoSprite.y += 1;
          break;
        case "ArrowRight":
          photoSprite.x += 1;
          break;
        case "ArrowLeft":
          photoSprite.x -= 1;
          break;
        case "Minus":
          handleScale("reduce");
          break;
        case "BracketLeft":
          handleScale("reduce");
          break;
        case "BracketRight":
          handleScale("add");
          break;
        case "Equal":
          handleScale("add");
          break;
        case "Enter":
          const editPhoto = await getImg(container);
          setPhotoList((oldList) =>
            oldList.map((item) => {
              if (item.id === showPhotoRef.current?.id || "") {
                return {
                  ...item,
                  editPhoto,
                };
              }
              return item;
            })
          );
          break;
        default:
          break;
      }
    },
    [getImg, handleScale, setPhotoList]
  );

  // 渲染图片列表
  const renderPhotoList = useCallback(async () => {
    const newPhotoList: IPhotoData[] = [];
    let update = false;
    for (let index = 0; index < photoList.length; index++) {
      const item = photoList[index];
      if (item.canvasData) {
        newPhotoList.push(item);
        continue;
      }
      update = true;
      const show = showPhoto?.id === item.id;
      const newData = await renderPhotoContainer(item, show);
      if (show) {
        showPhotoRef.current = { ...newData };
      }
      newPhotoList.push(newData);
    }
    if (update) {
      setPhotoList(newPhotoList);
      const { canvasData } = showPhotoRef.current!;
      if (canvasData) canvasData.container.visible = true;
      await resetCanvasSize(showPhotoRef.current!);
    }
  }, [
    photoList,
    showPhoto?.id,
    renderPhotoContainer,
    setPhotoList,
    resetCanvasSize,
  ]);

  // 初始化画布
  useEffect(() => {
    initCanvas();
    document.body.addEventListener("keydown", handleKeyboardEvent, false);
    return () => {
      document.body.removeEventListener("keydown", handleKeyboardEvent);
    };
  }, [handleKeyboardEvent, initCanvas]);

  // 列表变化 渲染列表
  useEffect(() => {
    renderPhotoList();
  }, [renderPhotoList]);

  // 监听切换，切换显示画布
  useEffect(() => {
    if (!showPhoto) return;
    setPhotoList((oldList) =>
      oldList.map((item, index) => {
        if (item.canvasData) {
          const show = showPhoto.id === item.id;
          item.canvasData.container.visible = show;
          if (show) {
            showPhotoRef.current = { ...showPhoto };
            resetCanvasSize(item);
          }
        }
        return item;
      })
    );
  }, [showPhoto, setPhotoList, resetCanvasSize]);

  useImperativeHandle(
    ref,
    () => {
      return {
        renderPhotoContainer,
      };
    },
    [renderPhotoContainer]
  );

  return <div ref={canvasContainerRef} className={styles.canvasBox}></div>;
});

export default memo(Canvas);
export type { ICanvasRef };
